#include "mycache.h"
#include "cache_ref.h"

CacheRefModel::CacheRefModel(MyCache *_top, size_t memory_size)
	: top(_top), scope(top->VCacheTop), mem(memory_size)
{
	mem.set_name("ref");
#ifdef REFERENCE_CACHE
	// REFERENCE_CACHE does nothing else
#else
	/**
	 * TODO (Lab3) setup reference model :)
	 */
	reset();

#endif
}

void CacheRefModel::reset()
{
	log_debug("ref: reset()\n");
	mem.reset();
#ifdef REFERENCE_CACHE
	// REFERENCE_CACHE does nothing else
#else
	/**
	 * TODO (Lab3) reset reference model :)
	 */

	// reset meta_ram
	for(int i = 0; i < 8; i++){
		meta_ram[i].cnt = 0;
		for(int j = 0; j < 2; j++){
			meta_ram[i].meta[j].valid = 0;
			meta_ram[i].meta[j].dirty = 0;
			meta_ram[i].meta[j].age = 0;
		}
	}
	// reset data_ram
	for(int i = 0; i < 16; i++)
		for(int j = 0; j < 16; j++)
			data_ram[i][j] = 0;
		
#endif
}

auto CacheRefModel::load(addr_t addr, AXISize size) -> word_t
{
	log_debug("ref: load(0x%lx, %d)\n", addr, 1 << size);
#ifdef REFERENCE_CACHE
	addr_t start = addr / 128 * 128;
	for (int i = 0; i < 16; i++) {
		buffer[i] = mem.load(start + 8 * i);
	}

	return buffer[addr % 128 / 8];
#else
	/**
	 * TODO (Lab3) implement load operation for reference model :)
	 */

	// decode addr
	addr_t tag = addr / 1024;
	addr_t index = (addr / 128) % 8;
	addr_t offset = (addr % 128) / 8;
	// get set
	set_t set = meta_ram[index];
	// try to find meta, if failed, replace one
	bool hit = 0, is_replaced = 0;
	int pos = 0, max = 0;
	meta_t meta;
	for(int i = 0; i < 2; i++)
		if(set.meta[i].valid && set.meta[i].tag == tag){
			hit = 1;
			pos = i;
			break;
		}
	if(!hit){
		is_replaced = 1;
		for(int i = 0; i < 2; i++){
			if(!set.meta[i].valid){
				pos = i;
				break;
			} else {
				if(max < set.meta[i].age){
					max = set.meta[i].age;
					pos = i;
				}
			}
		}
	}
	meta = set.meta[pos];
	// if is_replaced and dirty, write back
	auto mask1 = STROBE_TO_MASK[0xf];
	auto mask = (mask1 << 32) | mask1;
	if(is_replaced && meta.dirty)
		for(int i = 0; i < 16; i++)
			mem.store(meta.tag * 1024 + index * 128 + 8 * i, data_ram[index * 2 + pos][i], mask);
	// if is_replaced, fetch
	if(is_replaced)
		for(int i = 0; i < 16; i++)
			data_ram[index * 2 + pos][i] = mem.load(tag * 1024 + index * 128 + 8 * i);
	// update meta_ram
	meta_ram[index].cnt = pos;
	for(int i = 0; i < 2; i++)
		if(i != pos && meta_ram[index].meta[i].valid && meta_ram[index].meta[i].age < meta.age)
			meta_ram[index].meta[i].age++;
	meta_ram[index].meta[pos].valid = 1;
	meta_ram[index].meta[pos].dirty = is_replaced ? 0 : meta_ram[index].meta[pos].dirty;
	meta_ram[index].meta[pos].age = 0;
	meta_ram[index].meta[pos].tag = is_replaced ? tag : meta_ram[index].meta[pos].tag;
	
	return data_ram[index * 2 + pos][offset];

#endif
}

void CacheRefModel::store(addr_t addr, AXISize size, word_t strobe, word_t data)
{

	log_debug("ref: store(0x%lx, %d, %x, \"%016x\")\n", addr, 1 << size, strobe, data);
#ifdef REFERENCE_CACHE
	addr_t start = addr / 128 * 128;
	for (int i = 0; i < 16; i++) {
		buffer[i] = mem.load(start + 8 * i);
	}

	auto mask1 = STROBE_TO_MASK[strobe & 0xf];
	auto mask2 = STROBE_TO_MASK[((strobe) >> 4) & 0xf];
	auto mask = (mask2 << 32) | mask1;
	auto &value = buffer[addr % 128 / 8];
	value = (data & mask) | (value & ~mask);
	mem.store(addr, data, mask);
	return;
#else
	/**
	 * TODO (Lab3) implement store operation for reference model :)
	 */

	// decode addr
	addr_t tag = addr / 1024;
	addr_t index = (addr / 128) % 8;
	addr_t offset = (addr % 128) / 8;
	// get set
	set_t set = meta_ram[index];
	// try to find meta, if failed, replace one
	bool hit = 0, is_replaced = 0;
	int pos = 0, max = 0;
	meta_t meta;
	for(int i = 0; i < 2; i++)
		if(set.meta[i].valid && set.meta[i].tag == tag){
			hit = 1;
			pos = i;
			break;
		}
	if(!hit){
		is_replaced = 1;
		for(int i = 0; i < 2; i++){
			if(!set.meta[i].valid){
				pos = i;
				break;
			} else {
				if(max < set.meta[i].age){
					max = set.meta[i].age;
					pos = i;
				}
			}
		}
	}
	meta = set.meta[pos];
	// if is_replaced and dirty, write back
	auto mask1 = STROBE_TO_MASK[0xf];
	auto mask = (mask1 << 32) | mask1;
	if(is_replaced && meta.dirty)
		for(int i = 0; i < 16; i++)
			mem.store(meta.tag * 1024 + index * 128 + 8 * i, data_ram[index * 2 + pos][i], mask);
	// if is_replaced, fetch and update data_ram
	if(is_replaced)
		for(int i = 0; i < 16; i++)
			data_ram[index * 2 + pos][i] = mem.load(tag * 1024 + index * 128 + 8 * i);
	// update meta_ram
	meta_ram[index].cnt = pos;
	for(int i = 0; i < 2; i++)
		if(i != pos && meta_ram[index].meta[i].valid && meta_ram[index].meta[i].age < meta.age)
			meta_ram[index].meta[i].age++;
	meta_ram[index].meta[pos].valid = 1;
	meta_ram[index].meta[pos].dirty = 1;
	meta_ram[index].meta[pos].age = 0;
	meta_ram[index].meta[pos].tag = is_replaced ? tag : meta_ram[index].meta[pos].tag;
	// update data_ram
	auto mask1u = STROBE_TO_MASK[strobe & 0xf];
	auto mask2u = STROBE_TO_MASK[((strobe) >> 4) & 0xf];
	auto masku = (mask2u << 32) | mask1u;
	data_ram[index * 2 + pos][offset] = (data & masku) | (data_ram[index * 2 + pos][offset] & (~masku));
	
	return;

#endif
}

void CacheRefModel::check_internal()
{
	log_debug("ref: check_internal()\n");
#ifdef REFERENCE_CACHE
	/**
	 * the following comes from StupidBuffer's reference model.
	 */
	for (int i = 0; i < 16; i++) {
		asserts(
			buffer[i] == scope->mem[i],
			"reference model's internal state is different from RTL model."
			" at mem[%x], expected = %016x, got = %016x",
			i, buffer[i], scope->mem[i]
		);
	}
#else
	/**
	 * TODO (Lab3) compare reference model's internal states to RTL model :)
	 *
	 * NOTE: you can use pointer top and scope to access internal signals
	 *       in your RTL model, e.g., top->clk, scope->mem.
	 */

#endif
}

void CacheRefModel::check_memory()
{
	log_debug("ref: check_memory()\n");
#ifdef REFERENCE_CACHE
	/**
	 * the following comes from StupidBuffer's reference model.
	 */
	asserts(mem.dump(0, mem.size()) == top->dump(), "reference model's memory content is different from RTL model");
#else
	/**
	 * TODO (Lab3) compare reference model's memory to RTL model :)
	 *
	 * NOTE: you can use pointer top and scope to access internal signals
	 *       in your RTL model, e.g., top->clk, scope->mem.
	 *       you can use mem.dump() and MyCache::dump() to get the full contents
	 *       of both memories.
	 */

#endif
}
